/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.swing.table;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;

import org.isqlviewer.sql.ByteArrayBlob;
import org.isqlviewer.sql.ByteArrayClob;
import org.isqlviewer.sql.ResultSetRenderer;

/**
 * @author Markus A. Kobold &lt;mkobold at sprintpcs dot com&gt;
 * @version 1.0
 */
public class ResultSetTableModel extends EnhancedTableModel {

    public ResultSetTableModel() {

        super(new String[0]);
    }

    public synchronized void setResults(ResultSet resultSet, ResultSetRenderer renderer) throws SQLException {

        clearAllData();
        try {
            int preferredMaxRows = Integer.MAX_VALUE;
            int columnCount = 0;
            ResultSetMetaData metaData = resultSet.getMetaData();
            Statement statement = resultSet.getStatement();
            try {
                preferredMaxRows = statement.getMaxRows();
                preferredMaxRows = preferredMaxRows <= 0 ? Integer.MAX_VALUE : preferredMaxRows;
            } catch (SQLException warning) {
            }

            columnCount = metaData.getColumnCount();
            if (columnCount <= 0) {
                // nothing to show //
                return;
            }
            columns.clear();
            for (int i = 0; i < columnCount; i++) {
                try {
                    columns.add(metaData.getColumnLabel(i + 1));
                } catch (SQLException warning) {
                    columns.add(metaData.getColumnName(i + 1));
                }
            }

            // make sure we are at the front of the results only if the result set is 'scrollable'
            try {
                int resultSetType = resultSet.getType();
                if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
                    if (resultSet.isBeforeFirst()) {
                        resultSet.beforeFirst();
                    }
                }
            } catch (SQLException warning) {
            }

            int rowsExtracted = 0;
            rowLoop : while (rowsExtracted < preferredMaxRows && resultSet.next()) {
                if (renderer.isCancelled()) {
                    break;
                }
                ArrayList<Object> rowset = new ArrayList<Object>();
                for (int column = 0; column < columnCount; column++) {
                    int columnIndex = column + 1;
                    int columnType = metaData.getColumnType(columnIndex);
                    boolean isFirstRow = rowsExtracted == 0;
                    try {
                        rowset.add(extractColumn(resultSet, columnIndex, columnType, isFirstRow));
                    } catch (IOException warning) {
                        rowset.clear();
                        continue rowLoop;
                    }
                }
                dataStore.add(rowset);
                // give other threads a chance to do something like interrupt
                rowsExtracted++;
                Thread.yield();
            }
        } finally {
            fireTableStructureChanged();
        }
    }

    private Object extractColumn(ResultSet resultSet, int columnIndex, int columnType, boolean isFirstRow) throws SQLException,
            IOException {

        Class columnClass = Object.class;
        Object columnValue = null;
        switch (columnType) {
            case Types.DATE :
                columnClass = Date.class;
                columnValue = resultSet.getDate(columnIndex);
                break;
            case Types.TIMESTAMP :
                columnClass = Timestamp.class;
                columnValue = resultSet.getTimestamp(columnIndex);
                break;
            case Types.TIME :
                columnClass = Time.class;
                columnValue = resultSet.getTime(columnIndex);
                break;
            case Types.TINYINT :
                columnClass = Byte.class;
                columnValue = new Byte(resultSet.getByte(columnIndex));
                break;
            case Types.SMALLINT :
                columnClass = Short.class;
                columnValue = new Short(resultSet.getShort(columnIndex));
                break;
            case Types.INTEGER :
                columnClass = Integer.class;
                columnValue = new Integer(resultSet.getInt(columnIndex));
                break;
            case Types.DECIMAL :
            case Types.NUMERIC :
                columnClass = BigDecimal.class;
                columnValue = resultSet.getBigDecimal(columnIndex);
                break;
            case Types.REAL :
                columnClass = Float.class;
                columnValue = new Float(resultSet.getFloat(columnIndex));
                break;
            case Types.DOUBLE :
            case Types.FLOAT :
                columnClass = Double.class;
                columnValue = new Double(resultSet.getDouble(columnIndex));
                break;
            case Types.BIGINT :
                columnClass = Long.class;
                columnValue = new Long(resultSet.getLong(columnIndex));
                break;
            case Types.BIT :
            case Types.BOOLEAN :
                columnClass = Boolean.class;
                columnValue = Boolean.valueOf(resultSet.getBoolean(columnIndex));
                break;
            case Types.LONGVARBINARY :
                InputStream binaryStream = resultSet.getBinaryStream(columnIndex);
                columnClass = Blob.class;
                columnValue = binaryStream == null ? null : new ByteArrayBlob(binaryStream);
                break;
            case Types.VARBINARY :
            case Types.BINARY :
                columnClass = Blob.class;
                columnValue = new ByteArrayBlob(resultSet.getBytes(columnIndex));
                break;
            case Types.LONGVARCHAR :
                Reader characterReader = resultSet.getCharacterStream(columnIndex);
                columnClass = Clob.class;
                columnValue = characterReader == null ? null : new ByteArrayClob(characterReader);
                break;
            case Types.VARCHAR :
            case Types.CHAR :
                columnClass = String.class;
                columnValue = resultSet.getString(columnIndex);
                break;
            case Types.BLOB :
                columnClass = Blob.class;
                columnValue = resultSet.getBlob(columnIndex);
                break;
            case Types.CLOB :
                columnClass = Clob.class;
                columnValue = resultSet.getClob(columnIndex);
                break;
            case Types.REF :
                columnClass = Ref.class;
                columnValue = resultSet.getRef(columnIndex);
                break;
            case Types.ARRAY :
                columnClass = Array.class;
                columnValue = resultSet.getArray(columnIndex);
                break;
            default :
                columnClass = Object.class;
                columnValue = resultSet.getObject(columnIndex);
                break;
        }

        if (isFirstRow) {
            setClassforColumn(columnIndex, columnClass);
        }

        if (resultSet.wasNull()) {
            return null;
        }
        return columnValue;
    }
}